﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Urbini.Fabio._5BI.BattagliaNavale
{
    /// <summary>
    /// TODO: DataBinding delle quattro label con flotta.NaviRimaste
    /// </summary>
    /// 
    // ATTRIBUTI - PROPRIETA' - CLASSI GLOBALI
    public enum StatoCella
    {
        NonDefinito = 0x0,
        NavePresente = 0x1,
        Invisibile = 0x2,
        InvisibilePresente = 0x3,
        Affondata = 0x4,
        Acqua = 0x8,
        Colpita = 0x10
    }
    public enum TipoNave { NonDefinito, DaUno, DaDue, DaTre, DaQuattro }
    public enum DirezioneNave { NonDefinito, Sinistra, Su, Destra, Giu }

    /// <summary>
    /// Ogni giocatore ha una mappa e una flotta. 
    /// </summary>
    public class Giocatore
    {
        string Name { get; set; }
        public Flotta flotta;
        public Mappa mappa;

        public Posizioni PosizioniCliccate { get; set; }
        public Giocatore(string Name, Flotta flotta)
        {
            this.Name = Name;
            this.flotta = flotta;
            mappa = new Mappa(flotta);
            PosizioniCliccate = new Posizioni();
        }
        public Giocatore(Flotta Flotta)
        {
            flotta = new Flotta();
            foreach (Nave nave in Flotta)
            {
                Nave temp = new Nave(nave.Tipo, nave.PosizioneIniziale, nave.Direzione);
                flotta.Add(temp);
            }
            mappa = new Mappa(flotta);
            PosizioniCliccate = new Posizioni();
        }
        public void AggiungiNave(Nave nave)
        {
            flotta.Add(nave);
            Aggiorna();
        }
        public StatoCella ClickOnXY(Posizione posizione)
        {
            StatoCella retVal;
            StatoCella controllo = mappa[posizione.Indice].Stato;

            // Cercare nella flotta una cella che abbia la stessa posizione
            // Chiamare metodo aggiorna della mappa

            mappa[posizione.Indice].Stato = retVal = flotta.ColpisciNave(posizione);
            if (controllo != retVal)
            {
                PosizioniCliccate.Add(posizione);
                return retVal;
            }
            else throw new PosizioneGiaCliccata("Posizione già cliccata");
        }
        public StatoCella ClickOnXY(Cella cella)
        {
            StatoCella retVal = StatoCella.Acqua;
            // Controllare cella.Posizione
            // Confrontare Posizione con quella di ogni cella di ogni nave della flotta
            mappa[cella.Posizione.Indice].Stato = retVal = flotta.ColpisciNave(cella);
            PosizioniCliccate.Add(cella.Posizione);
            return retVal;
        }
        public void Aggiorna()
        {
            mappa.Aggiorna(flotta);
        }
    }
    public class Mappa : List<Cella>
    {
        /// <summary>
        /// Aggiungere proprietà che restituisca le celle visibili dal giocatore
        /// Aggiungere proprietà che restituisca le celle visibili dall'avversario
        /// </summary>
        public Mappa(Flotta flotta)
        {
            for (int i = 0; i < Global.MAX_CELLE; i++)
            {
                Add(new Cella(i));
            }
            foreach (Nave nave in flotta)
                foreach (Cella cella in nave)
                    for (int i = 0; i < this.Count; i++)
                        if (cella.Posizione.Indice == this[i].Posizione.Indice) this[i].Stato = cella.Stato;
        }
        public Mappa(Mappa mappa)
        {
            foreach (Cella cella in mappa)
                this.Add(cella);
        }
        public Mappa() { }

        public void Aggiorna(Flotta flotta)
        {
            foreach (Nave nave in flotta)
                foreach (Cella cella in nave)
                    for (int i = 0; i < this.Count; i++)
                        if (cella.Posizione.Indice == this[i].Posizione.Indice) this[i].Stato = cella.Stato;
        }
        public Mappa MappaGiocatore(bool MappaVistaDaGiocatore)
        {
            if (MappaVistaDaGiocatore) return this;

            Mappa m = new Mappa(this);

            // Controllo di ogni cella all'interno della mappa
            // Se la cella corrente ha il flag NavePresente, allora lo stato di quella casella sarà Invisibile e Presente
            for (int i = 0; i < this.Count; i++)
                if (this[i].Stato.HasFlag(StatoCella.NavePresente)) m[i].Stato = StatoCella.InvisibilePresente;

            return m;
        }
    }
    public class Flotta : List<Nave>
    {
        // Attributi
        bool flottaIsPronta = false;
        Dictionary<TipoNave, int> naviRimaste;

        // Proprietà
        public bool FlottaPronta { get { return flottaIsPronta; } }
        public Dictionary<TipoNave, int> NaviRimaste { get { return naviRimaste; } }
        public bool FlottaAffondata
        {
            get
            {
                // Questo metodo restituisce "true" se la flotta è affondata
                bool ReturnValue = true;

                foreach (Nave nave in this)
                    if (!nave.NaveAffondata) { ReturnValue = false; break; }

                return ReturnValue;
            }
        }

        // Costruttori
        public Flotta()
        {
            RicreaDizionario();
        }

        #region Metodi


        public StatoCella ColpisciNave(Cella cellaNave)
        {
            // Il risultato è true se la nave è colpita
            StatoCella retVal = StatoCella.Acqua;

            foreach (Nave nave in this)
                retVal = nave.ColpisciCella(cellaNave);
           
            // La mappa può sfruttare questo metodo per assegnare lo stato alla casella.
            return retVal;
        }
        public StatoCella ColpisciNave(Posizione posizione)
        {
            StatoCella retVal = StatoCella.Acqua;

            foreach (Nave nave in this)
            {
                retVal = nave.ColpisciCella(posizione);
                if (retVal == StatoCella.Colpita || retVal == StatoCella.Affondata) return retVal;
            }

            return retVal;
        }
        public new void Add(Nave nave) 
        {
            // Una nave può essere aggiunta se :
            // 1 ) La posizione inserita è corretta
            // 2 ) Il numero di navi inseribili rientra nel numero definito
            if (!flottaIsPronta)
            {
                if (NumeroNaviOK(nave) & ControlloPosizionamentoNaveOK(nave)) base.Add(nave);
                ControlloNaviRimaste();
            }   
        }
        bool NumeroNaviOK(Nave Nave)
        {
            // Questo metodo deve controllare che la flotta rispetti le regole dettate
            // dalla classe Global (nei riguardi del numero di navi).
            // Non è necessario cambiare questo metodo. Basta cambiare la classe Global. 
            bool TuttoOK = true;

            // Variabili temporanee di controllo
            int Uno = 0, Due = 0, Tre = 0, Quattro = 0;

            foreach (Nave nave in this)
            {
                TipoNave tipo = nave.Tipo;
                switch (tipo)
                {
                    case TipoNave.DaUno: Uno++; break;
                    case TipoNave.DaDue: Due++; break;
                    case TipoNave.DaTre: Tre++; break;
                    case TipoNave.DaQuattro: Quattro++; break;
                    default: throw new UndefinedShipException("Definire una grandezza per la nave");
                }
            }
            switch (Nave.Tipo)
            {
                case TipoNave.DaUno: Uno++; break;
                case TipoNave.DaDue: Due++; break;
                case TipoNave.DaTre: Tre++; break;
                case TipoNave.DaQuattro: Quattro++; break;
                default: throw new UndefinedShipException("Definire una grandezza per la nave");
            }

            if (Uno > Global.NaviDaUno)
                throw new ShipsNumberException("Superato il numero di navi da uno. Il numero massimo è " + Convert.ToString(Global.NaviDaUno));
            if (Due > Global.NaviDaDue)
                throw new ShipsNumberException("Superato il numero di navi da due. Il numero massimo è " + Convert.ToString(Global.NaviDaDue));
            if (Tre > Global.NaviDaTre)
                throw new ShipsNumberException("Superato il numero di navi da tre. Il numero massimo è " + Convert.ToString(Global.NaviDaTre));
            if (Quattro > Global.NaviDaQuattro)
                throw new ShipsNumberException("Superato il numero di navi da quattro. Il numero massimo è " + Convert.ToString(Global.NaviDaQuattro));
            // Se le condizioni sono rispettate, la flotta è pronta
            if (Uno == Global.NaviDaUno && Due == Global.NaviDaDue && Tre == Global.NaviDaTre && Quattro == Global.NaviDaQuattro)
                flottaIsPronta = true;

            return TuttoOK;
        }
        /// <summary>
        /// Metodo per il controllo della posizione della nave
        /// </summary>
        /// <param name="NaveAggiunta"></param>
        /// <returns></returns>
        bool ControlloPosizionamentoNaveOK(Nave NaveAggiunta)
        {
            bool OK = true;
            
            // Viene creato un array temporaneo contenente le posizioni 
            int[] IndiciNaviAggiunte = new int[NaveAggiunta.Count];
            for (int i = 0; i < IndiciNaviAggiunte.Count(); i++)
            {
                IndiciNaviAggiunte[i] = NaveAggiunta[i].Posizione.Indice;
            }
            for (int i = 0; i < IndiciNaviAggiunte.Count(); i++)
                foreach (Nave nave in this)
                    foreach (Cella cella in nave)
                        // In questo caso è giò presente una cella con l'indice dato.
                        if (cella.Posizione.Indice == IndiciNaviAggiunte[i])
                        {
                            OK &= false;
                            throw new ShipDuplicatedException("Nave già presente. E' possibile che una nuova nave intersechi una già presente");
                        }
            return OK;
        }
        void ControlloNaviRimaste()
        {
            // Questo metodo viene chiamato ogni volta dal metodo Add
            RicreaDizionario();
            foreach (Nave n in this)
                naviRimaste[n.Tipo]--;
        }
        void RicreaDizionario()
        {
            // Metodo chiamato da ControlloNaviRimaste
            naviRimaste = new Dictionary<TipoNave, int>() 
            {
                { TipoNave.DaUno, Global.NaviDaUno }, 
                { TipoNave.DaDue, Global.NaviDaDue },
                {TipoNave.DaTre, Global.NaviDaTre }, 
                {TipoNave.DaQuattro, Global.NaviDaQuattro } 
            };
        }

        #endregion
    }
    public class Nave : List<Cella>
    {
        // Attributi pubblici
        public TipoNave Tipo { get; set; }
        public DirezioneNave Direzione { get; set; }  
        public Posizione PosizioneIniziale;
        public bool NaveAffondata
        {
            get
            {
                bool RetVal = true;

                foreach (Cella cella in this)
                    if (!(cella.Stato == StatoCella.Affondata))
                        return false;
                return RetVal;
            }
        }

        // Costruttori Nave
        public Nave(TipoNave tipo, Posizione PosizioneIniziale, DirezioneNave Direzione)
        {
            this.Tipo = tipo;
            this.Direzione = Direzione;
            this.PosizioneIniziale = PosizioneIniziale;

            int Grandezza = (int)tipo;
            int x = PosizioneIniziale.X;
            int y = PosizioneIniziale.Y;

            // Posizionamento nave durante la creazione. 
            for (int i = 0; i < Grandezza; i++)
            {
                // Se le coordinate della nave rientrano nei parametri...
                if (CoordinataOK(x, y))
                {
                    // Aggiunta una nuova cella alla nave
                    Add(new Cella(x, y));
                    this[i].Stato = StatoCella.NavePresente;

                    switch (Direzione)
                    {
                        case DirezioneNave.Sinistra: x--; break;
                        case DirezioneNave.Su: y--; break;
                        case DirezioneNave.Destra: x++; break;
                        case DirezioneNave.Giu: y++; break;
                        default: throw new UndefinedShipException("Direzione della nave non definita");
                    }
                }
                // T4 else ... la nave non può esistere --> Chiamare distruttore ?
            }
        }
        public Nave(Posizioni Posizioni)
        {  
            if (Posizioni.Count == 1) 
            {
                Direzione = DirezioneNave.Destra;
                PosizioneIniziale = Posizioni[0];
                Tipo = (TipoNave)1;
                if (CoordinataOK(PosizioneIniziale.X, PosizioneIniziale.Y))
                    Add(new Cella(PosizioneIniziale.X, PosizioneIniziale.Y));
            }
            if (Posizioni.Count > 1)
            {
                PosizioneIniziale = Posizioni[0];
                Tipo = (TipoNave)Posizioni.Count;
                int differenza = Posizioni[1].Indice - Posizioni[0].Indice;
                switch (differenza)
                {
                    case 1: Direzione = DirezioneNave.Destra; break;
                    case -1: Direzione = DirezioneNave.Sinistra; break;
                    case 8: Direzione = DirezioneNave.Giu; break;
                    case -8: Direzione = DirezioneNave.Su; break;
                    default: throw new Exception("Differenza non valida");
                }

                foreach (Posizione p in Posizioni)
                {
                    int x = p.X;
                    int y = p.Y;
                    
                    if (CoordinataOK(x,y))
                        Add(new Cella(x, y));
                }
            }

        }
        /// <summary>
        /// Metodo per verificare la Coordinata della nave (per ogni singola cella)
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns> Restituisce true se la coordinata è accettabile </returns>
        bool CoordinataOK(int x, int y)
        {
            bool OK = true;
            // La nave è posizionata orizzontalmente oltre i limiti della matrice (o prima di 0)
            if (x < 0)
            {
                OK = false;
                throw new OutOfBattlefieldException("La nave supera il limite sinistro del campo di battaglia");
            }
            if (y < 0)
            {
                OK = false;
                throw new OutOfBattlefieldException("La nave supera il limite superiore del campo di battaglia");
            }
            if (x > Global.COLONNE - 1)
            {
                OK = false;
                throw new OutOfBattlefieldException("La nave supera il limite destro del campo di battaglia");
            }

            if (y > Global.RIGHE - 1)
            {
                OK = false;
                throw new OutOfBattlefieldException("La nave supera il limite inferiore del campo di battaglia");
            }
            return OK;
        }
        public StatoCella ColpisciCella(Posizione posizione)
        {
            StatoCella RetValue = StatoCella.Acqua;
            foreach (Cella cella in this)
                if (cella.Posizione.Indice == posizione.Indice)
                {
                    cella.Stato = StatoCella.Colpita;
                    return RetValue = Aggiorna(); 
                }
            // Dato che la flotta ha subito una modifica, viene fatto un controllo 
            // chiamando il metodo Aggiorna.
            return RetValue;
        }
        public StatoCella ColpisciCella(Cella cellaNave)
        {
            StatoCella RetValue = StatoCella.Acqua;

            foreach (Cella cella in this)
                if (cella.Posizione == cellaNave.Posizione)
                {
                    cella.Stato = StatoCella.Colpita;
                    return RetValue = Aggiorna(); 
                }

            return RetValue;
        }
        StatoCella Aggiorna()
        {
            // Questo metodo aggiorna lo Stato delle celle della nave.
            bool temp = true;
            StatoCella res = StatoCella.Colpita;

            // Scorrimento di tutte le celle. Se tutte le celle sono colpite, temp rimane vero
            // Altrimenti temp diventa falso
            foreach (Cella cella in this)
            {
                if (cella.Stato == StatoCella.Colpita || cella.Stato == StatoCella.Affondata) { temp &= true; }
                else { temp = false; break; }
            }
            // Se temp è ancora vero, viene cambiato lo Stato delle celle in "Affondata".
            if (temp)
            {
                foreach (Cella cella in this)
                    res = cella.Stato = StatoCella.Affondata;
            }
            return res;
        }
    }
    public class Cella
    {
        public StatoCella Stato;
        public Posizione Posizione { get; set; }

        public Cella() { }

        public Cella(int x, int y)
        {
            this.Posizione = new Posizione(x, y);
        }

        public Cella(int indice)
        {
            this.Posizione = new Posizione(indice);
        }
    }

    public class Posizioni : List<Posizione>
    {
        public Posizioni() { }
        public Posizioni(List<Posizione> Posizioni) { AddRange(Posizioni); }
        public Posizioni(IEnumerable<Posizione> Posizioni) { AddRange(Posizioni); }
        public Posizioni(List<int> Indici)
        {
            foreach (int indice in Indici)
                Add(new Posizione(indice));
        }
    }
    public class Posizione
    {
        public int X { get; set; }
        public int Y { get; set; }
        public int Indice { get { return Y * Global.COLONNE + X; } }
        public Posizione() { }
        public Posizione(int X, int Y) { this.X = X; this.Y = Y; }
        public Posizione(int Indice)
        {
            X = Indice % Global.RIGHE;
            Y = Indice / Global.RIGHE;
        }
        public bool PosizioneValida(int xMax, int yMax)
        {
            if (X <= xMax && Y <= yMax && X >= 0 && Y >= 0) return true;
            return false;
        }
    }

    #region Eccezioni 

    public class OutOfBattlefieldException : Exception
    {
        private string p;

        public override string Message { get { return p; } }

        public OutOfBattlefieldException(string p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }
    }
    public class ShipsNumberException : Exception
    {
        private string p;

        public override string Message { get { return p; } }

        public ShipsNumberException(string p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }
    }
    public class ShipDuplicatedException : Exception
    {
        private string p;

        public override string Message { get { return p; } }

        public ShipDuplicatedException(string p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }
    }
    public class UndefinedShipException : Exception
    {
        private string p;

        public override string Message { get { return p; } }

        public UndefinedShipException(string p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }
    }
    public class PosizioneGiaCliccata : Exception
    {
        private string p;

        public override string Message { get { return p; } }

        public PosizioneGiaCliccata(string p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }
    }

    #endregion
}